﻿using System;
using System.Net;
using System.IO;
using System.Xml.Linq;

namespace CannedHeater.Model.Utilities.Pachube
{
    /// <summary>
    /// Making requests to the Pachube server
    /// </summary>
    public class PachubeRequest
    {
        #region Locally used variables
        /// <summary>
        /// Pachube feed id
        /// </summary>
        private int _feedID;

        /// <summary>
        /// Duration parameter for pachube (Example: "3hours")
        /// </summary>
        private string _duration;

        /// <summary>
        /// Interval parameter for pachube, specified in seconds
        /// </summary>
        private int _interval; 
        #endregion

        #region Types
        /// <summary>
        /// Result status
        /// </summary>
        public enum StatusType
        {
            /// <summary>
            /// Request completed successfully
            /// </summary>
            Success,
            /// <summary>
            /// Web Request could not be completed - could be no reply
            /// </summary>
            WebError,
            /// <summary>
            /// Not a valid XML Document found
            /// </summary>
            PachubeError,
            /// <summary>
            /// Malformed document found
            /// </summary>
            Malformed,
            /// <summary>
            /// No internet connection available
            /// </summary>
            NoInternetConnection,
            /// <summary>
            /// Pachube API Key has not been specified
            /// </summary>
            PachubeKeyNotSpecified
        }

        /// <summary>
        /// The result of a Pachube request is "returned" with these event arguments
        /// </summary>
        public class FetchDataArgs : EventArgs
        {
            private StatusType _status = StatusType.Success; // Default status is success
            private string _errorMsg = null; // Custom error message
            private XDocument _doc = null; // The raw document fetched from pachube

            /// <summary>
            /// Request result
            /// </summary>
            public StatusType Status
            {
                get { return _status; }
            }

            /// <summary>
            /// Error message
            /// </summary>
            public string Error
            {
                get { return _errorMsg; }
            }

            /// <summary>
            /// Document fetched from pachube
            /// </summary>
            public XDocument Doc
            {
                get { return _doc; }
            }

            /// <summary>
            /// Constructor for requests with an errornous result
            /// </summary>
            /// <param name="type">StatusType representing the error</param>
            /// <param name="error">String describing the error</param>
            public FetchDataArgs(StatusType type, string error)
            {
                _status = type;
                _errorMsg = error;
            }

            /// <summary>
            /// Constructor for requests with successfull result
            /// </summary>
            /// <param name="doc">The document that was fetched</param>
            public FetchDataArgs(XDocument doc)
            {
                _doc = doc;
            }

        }
        #endregion

        #region Events
        public delegate void FetchDataHandler(object sender, FetchDataArgs args);

        /// <summary>
        /// Event raised when pachube document has been fetched, or in case of error
        /// </summary>
        public event FetchDataHandler FetchDataCompleted;
        #endregion

        #region Properties
        /// <summary>
        /// Timezone to use when fetching data from pachube
        /// </summary>
        public string TimeZone
        {
            set;
            get;
        }

        private string _key;
        /// <summary>
        /// The Pachube API Key used for making the request
        /// </summary>
        /// <exception cref="KeyNotSpecifiedException">Thrown on get if key is not specified</exception>
        public string Key
        {
            set
            {
                _key = value;
            }
            private get
            {
                if (String.IsNullOrEmpty(_key))
                    throw new KeyNotSpecifiedException();
                return _key;
            }
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Create new Pachube Request
        /// </summary>
        public PachubeRequest()
        {
            // Default settings
            TimeZone = "Copenhagen";
        }

        /// <summary>
        /// Creates new Pachube Request with specified API key
        /// </summary>
        /// <param name="key">Pachube API Key</param>
        public PachubeRequest(string key) : this()
        {
            _key = key;
        }

        /// <summary>
        /// Creates new Pachube Request
        /// </summary>
        /// <param name="key">Pachube API Key</param>
        /// <param name="feedID">Pachube Feed ID</param>
        /// <param name="duration">Duration for pachube data (Example: "3hours")</param>
        /// <param name="interval">Interval for pachube data (in seconds)</param>
        public PachubeRequest(string key, int feedID, string duration, int interval) : this(key)
        {
            _feedID = feedID;
            _duration = duration;
            _interval = interval;
        }
        #endregion

        /// <summary>
        /// Start fetching data (raw document), using the parameters provided in the constructor for the object
        /// </summary>
        /// <remarks>Precondition: FeedID, duration and interval already applied (through constructor)</remarks>
        public void FetchRawData()
        {
            FetchRawData(_feedID, _duration, _interval);
        }

        /// <summary>
        /// Start fetching data (raw document), using provided parameters
        /// </summary>
        /// <param name="feedID">Pachube Feed ID</param>
        /// <param name="duration">Duration for pachube data (Example: "3hours")</param>
        /// <param name="interval">Interval for pachube data (in seconds)</param>
        public void FetchRawData(int feedID, string duration, int interval)
        {
            // Check if network is available
            if (!Microsoft.Phone.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
            {
                Error(StatusType.NoInternetConnection, "No internet connection available");
                return;
            }

            
            // Start the request for pachube, asynchronously

            try
            {
                Uri u = new Uri(
                    "http://api.pachube.com/v2/feeds/" + feedID.ToString() + ".xml?key=" + Key +
                    "&per_page=1000&duration=" + duration + "&interval=" + interval.ToString() + "&rand=" +
                    DateTime.Now.Ticks,
                    UriKind.Absolute);


                WebClient c = new WebClient();
                c.Headers[HttpRequestHeader.CacheControl] = "no-cache"; // Seems not to be enough, therefore added random garbage to uri at the end
                    
                c.OpenReadCompleted += new OpenReadCompletedEventHandler(FetchRawData_Completed);
                c.OpenReadAsync(u);
            }
            catch (KeyNotSpecifiedException)
            {
                Error(StatusType.PachubeKeyNotSpecified, "Pachube API Key not specified");
            }
        }

        /// <summary>
        /// Handles the reply from Pachube server
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FetchRawData_Completed(object sender, OpenReadCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                Error(StatusType.WebError,
                      e.Error.Message + "\n" +
                      "Either the request was malformed, the service is down, or there is no internet connection.");
            }
            else
            {
                // Get XDocument from result: http://msdn.microsoft.com/en-us/library/cc645034%28v=vs.95%29.aspx
                using (Stream s = e.Result)
                {
                    XDocument doc = null;
                    try
                    {
                        doc = XDocument.Load(s);
                    }catch
                    {
                        Error(StatusType.Malformed, "Not a valid XML Document");
                    }

                    #region Error handling
                    if (doc.Root == null)
                    {
                        Error(StatusType.Malformed, "No document root");
                    }
                    else if (doc.Root.Name == "errors")
                    {
                        // Pachube can give an error through an root with the tag name "errors"
                        // Never reached though, responsecode != 200 OK prevents result to be read, WebClient then gives Error NotFound no matter what :(
                        string msg = doc.Root.Element("error") == null ? "Unknown error" : doc.Root.Element("error").Value;
                        Error(StatusType.PachubeError, msg);
                    }
                    #endregion
                    else
                    {
                        // Success
                        if (FetchDataCompleted != null)
                            FetchDataCompleted(sender, new FetchDataArgs(doc));
                    }
                }
            }
        }

        #region Helpers
        /// <summary>
        /// Used internally to easily return an errormessage, with appropriate status type.
        /// Raises FetchDataCompleted with provided arguments
        /// </summary>
        /// <param name="errorType">Error status type</param>
        /// <param name="msg">Custom error message</param>
        private void Error(StatusType errorType, string msg)
        {
            FetchDataArgs args = new FetchDataArgs(errorType, msg);
            if (FetchDataCompleted != null)
            {
                FetchDataCompleted(null, args);
            }
        }
        #endregion

    }

    /// <summary>
    /// Exception used when Pachube API key is not specified
    /// </summary>
    public class KeyNotSpecifiedException : Exception { }
}
